﻿namespace MicroCloud.Authentication
{
    /// <summary>
    /// 身份认证模块基类
    /// </summary>
    /// <typeparam name="TUser">用户类型</typeparam>
    /// <typeparam name="TUserKey">用户编号类型</typeparam>
    /// <typeparam name="TRole">角色类型</typeparam>
    /// <typeparam name="TRoleKey">角色编号类型</typeparam>
    /// <typeparam name="TOrganization">组织机构类型</typeparam>
    /// <typeparam name="TOrganizationKey">组织机构编号类型</typeparam>
    /// <typeparam name="TUserToken">用户令牌类型</typeparam>
    /// <typeparam name="TUserTokenKey">用户令牌编号类型</typeparam>
    [Description("身份认证模块")]
    [DependsOnPacks(typeof(AspNetCorePack))]
    public abstract class AuthenticationPackBase<TUser, TUserKey, TRole, TRoleKey, TOrganization, TOrganizationKey, TUserToken, TUserTokenKey> : AspPackBase
        where TUser : UserBase<TUserKey>
        where TUserKey : struct, IEquatable<TUserKey>
        where TRole : RoleBase<TRoleKey, TUserKey>
        where TRoleKey : struct, IEquatable<TRoleKey>
        where TOrganization : OrganizationBase<TOrganizationKey, TUserKey>
        where TOrganizationKey : struct, IEquatable<TOrganizationKey>
        where TUserToken : UserTokenBase<TUserTokenKey, TUserKey>
        where TUserTokenKey : struct, IEquatable<TUserTokenKey>
    {
        //字段
        private MicroCloudOptions _options;
        private IServiceProvider _provider;
        private IDistributedCache _cache;

        #region "属性"
        #region "获取 模块级别"
        /// <summary>
        /// 获取 模块级别
        /// <para>级别越小越先启动</para>
        /// </summary>
        public override PackLevel Level => PackLevel.Framework;
        #endregion
        #region "获取 模块启动顺序编号"
        /// <summary>
        /// 获取 模块启动顺序编号
        /// <para>模块启动的顺序先按级别启动，级别内部再按此顺序启动，级别默认为0，表示无依赖，需要在同级别有依赖顺序的时候，再重写为 >0 的顺序值。</para>
        /// </summary>
        public override int OrderNo => 3;
        #endregion

        #endregion

        #region "方法"
        #region "将模块服务添加到依赖注入服务容器中"
        /// <summary>
        /// 将模块服务添加到依赖注入服务容器中
        /// </summary>
        /// <param name="services">依赖注入服务容器</param>
        /// <returns></returns>
        public override IServiceCollection AddServices(IServiceCollection services)
        {
            _options = services.GetMicroCloudOptions();
            services.TryAddScoped<IUserClaimsProvider, UserClaimsProvider<TUser, TUserKey, TRole, TRoleKey, TOrganization, TOrganizationKey>>();

            string defaultSchema = IdentityConstants.ApplicationScheme;
            if (_options.Jwt?.Enabled == true && _options.Cookie?.Enabled != true)
            {
                defaultSchema = JwtBearerDefaults.AuthenticationScheme;
            }
            AuthenticationBuilder builder = services.AddAuthentication(opts =>
            {
                opts.DefaultScheme = defaultSchema;
                opts.DefaultAuthenticateScheme = defaultSchema;
            });
            AddJwtBearer(services, builder);
            AddCookie(services, builder);
            AddOAuth2(services, builder);

            return services;
        }
        #endregion
        #region "应用模块服务"
        /// <summary>
        /// 应用模块服务
        /// </summary>
        /// <param name="app">应用程序</param>
        public override void UsePack(WebApplication app)
        {
            IServiceProvider provider = app.Services;

            _provider = provider;
            _cache = _provider.GetRequiredService<IDistributedCache>();

            app.UseAuthentication();

            IsEnabled = true;
        }
        #endregion

        #region "添加JwtBearer支持"
        /// <summary>
        /// 添加JwtBearer支持
        /// </summary>
        /// <param name="services"></param>
        /// <param name="builder"></param>
        /// <returns></returns>
        protected virtual AuthenticationBuilder AddJwtBearer(IServiceCollection services, AuthenticationBuilder builder)
        {
            JwtOption jwt = _options.Jwt;
            if (jwt?.Enabled != true)
            {
                return builder;
            }

            services.TryAddScoped<IJwtBearerService, JwtBearerService<TUser, TUserKey>>();
            builder.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme,
                opts =>
                {
                    string secret = jwt.Secret;
                    if (secret.IsMissing())
                    {
                        throw new Exception(I18N.T("配置文件中 MicroCloud 配置的 Jwt 节点的 Secret 不能为空"));
                    }
                    opts.TokenValidationParameters = new TokenValidationParameters()
                    {
                        ValidIssuer = jwt.Issuer.IsMissing() ? JwtConstants.DefaultIssuer : jwt.Issuer,
                        // ValidAudience = configuration["MicroCloud:Jwt:Audience"] ?? JwtConstants.DefaultAudience,
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret)),
                        LifetimeValidator = (nbf, exp, token, param) => exp > DateTime.UtcNow,
                        //动态验证的方式，用以处理token强制失效逻辑
                        AudienceValidator = (audiences, securityToken, validationParameters) =>
                        {
                            var audience = audiences.FirstOrDefault();

                            var result = _cache.GetString(audience);
                            return !result.IsMissing();
                            //var MicroCloudCacheService = ServiceLocator.Instance.GetService<IMicroCloudCacheService>();
                            //return MicroCloudCacheService.IsExisted(audience);
                        },
                    };

                    opts.Events = new MicroCloudJwtBearerEvents(services);
                });

            services.AddHostedService<JwtBearerHostedService<TUser, TUserKey, TUserToken, TUserTokenKey>>();

            return builder;
        }
        #endregion
        #region "添加Cookie支持"
        /// <summary>
        /// 添加Cookie支持
        /// </summary>
        /// <param name="services"></param>
        /// <param name="builder"></param>
        /// <returns></returns>
        protected virtual AuthenticationBuilder AddCookie(IServiceCollection services, AuthenticationBuilder builder)
        {
            var cookie = _options.Cookie;
            if (cookie?.Enabled != true)
            {
                return builder;
            }

            services.AddScoped<MicroCloudCookieAuthenticationEvents>();
            builder.AddIdentityCookies(b =>
            {
                b.ApplicationCookie.Configure(opts =>
                {
                    if (cookie.CookieName != null)
                    {
                        opts.Cookie.Name = cookie.CookieName;
                    }

                    opts.LoginPath = cookie.LoginPath ?? opts.LoginPath;
                    opts.LogoutPath = cookie.LogoutPath ?? opts.LogoutPath;
                    opts.AccessDeniedPath = cookie.AccessDeniedPath ?? opts.AccessDeniedPath;
                    opts.ReturnUrlParameter = cookie.ReturnUrlParameter ?? opts.ReturnUrlParameter;
                    opts.SlidingExpiration = cookie.SlidingExpiration;
                    if (cookie.ExpireMins > 0)
                    {
                        opts.ExpireTimeSpan = TimeSpan.FromMinutes(cookie.ExpireMins);
                    }

                    opts.EventsType = typeof(MicroCloudCookieAuthenticationEvents);
                });
            });
            return builder;
        }
        #endregion
        #region "添加OAuth2第三方登录配置"
        /// <summary>
        /// 添加OAuth2第三方登录配置
        /// </summary>
        /// <param name="services"></param>
        /// <param name="builder"></param>
        /// <returns></returns>
        protected virtual AuthenticationBuilder AddOAuth2(IServiceCollection services, AuthenticationBuilder builder)
        {
            IDictionary<string, OAuth2Option> dict = _options.OAuth2;
            if (dict == null)
            {
                return builder;
            }

            foreach (var (name, options) in dict)
            {
                if (!options.Enabled)
                {
                    continue;
                }
                if (options.ClientId.IsMissing())
                {
                    throw new Exception(I18N.T("配置文件中 MicroCloud:OAuth2 配置的 {0} 节点的 ClientId 不能为空", name));
                }
                if (options.ClientSecret.IsMissing())
                {
                    throw new Exception(I18N.T("配置文件中 MicroCloud:OAuth2 配置的 {0} 节点的 ClientSecret 不能为空", name));
                }

                var nameLower = name.ToLower();
                switch (nameLower)
                {
                    case "wechat":
                        builder.AddWeChat(opts =>
                        {
                            opts.AppId = options.ClientId;
                            opts.AppKey = options.ClientSecret;
                        });
                        break;
                    case "dingtalk":
                        builder.AddDingTalk(opts =>
                        {
                            opts.AppId = options.ClientId;
                            opts.AppKey = options.ClientSecret;
                        });
                        break;
                    case "tiktok":
                        builder.AddTikTok(opts =>
                        {
                            opts.AppId = options.ClientId;
                            opts.AppKey = options.ClientSecret;
                        });
                        break;
                    case "qq":
                        builder.AddQQ(opts =>
                        {
                            opts.AppId = options.ClientId;
                            opts.AppKey = options.ClientSecret;
                        });
                        break;
                    case "microsoft":
                        builder.AddMicrosoftAccount(opts =>
                        {
                            opts.ClientId = options.ClientId;
                            opts.ClientSecret = options.ClientSecret;
                        });
                        break;
                    case "github":
                        builder.AddGitHub(opts =>
                        {
                            opts.ClientId = options.ClientId;
                            opts.ClientSecret = options.ClientSecret;
                        });
                        break;
                }
            }

            return builder;
        }
        #endregion

        #endregion

    }

}
